package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import cn.hutool.json.JSONUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.PipedReader;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;


    @Autowired
    @Qualifier("threadPoolTaskExecutor")
    private Executor threadPoolTaskExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 将指定专辑导入到索引库
     *
     * @param albumId
     * @return
     */
    @Override
    public void upperAlbum(Long albumId) {
        //1.创建索引库文档对象AlbumInfoIndex
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();


        //2.远程调用专辑服务查询专辑信息，封装专辑相关包括专辑标签列表 CompletableFuture默认线程池：ForkJoinPool.commonPool()
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑{}不存在", albumId);
            //2.1 封装专辑相关信息
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);

            //2.2 封装专辑标签列表
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(a -> BeanUtil.copyProperties(a, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);

        //3.远程调用专辑服务查询分类信息，封装1,2级分类ID
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "分类{}不存在", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolTaskExecutor);

        //4.远程调用用户服务查询主播信息，封装主播名称
        CompletableFuture<Void> userCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "主播{}不存在", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);

        //5.封装专辑统计数值及专辑热度
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {
            //5.1 随机生成四项统计数值
            int num1 = RandomUtil.randomInt(1000, 2000);
            int num2 = RandomUtil.randomInt(500, 1000);
            int num3 = RandomUtil.randomInt(200, 500);
            int num4 = RandomUtil.randomInt(100, 500);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);
            //5.2 基于四项统计数值计算热度 规则：统计数值*系数累加 播放量*0.1+订阅量*0.2+购买量*0.3+评论量*0.4
            BigDecimal bigDecimal1 = BigDecimal.valueOf(num1).multiply(BigDecimal.valueOf(0.1));
            BigDecimal bigDecimal2 = BigDecimal.valueOf(num2).multiply(BigDecimal.valueOf(0.2));
            BigDecimal bigDecimal3 = BigDecimal.valueOf(num3).multiply(BigDecimal.valueOf(0.3));
            BigDecimal bigDecimal4 = BigDecimal.valueOf(num4).multiply(BigDecimal.valueOf(0.4));
            BigDecimal bigDecimal = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);
            albumInfoIndex.setHotScore(bigDecimal.doubleValue());
        }, threadPoolTaskExecutor);


        //7.组合所有异步任务
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                categoryCompletableFuture,
                userCompletableFuture,
                statCompletableFuture
        ).join();

        //6.调用持久层接口保存文档到索引库
        albumInfoIndexRepository.save(albumInfoIndex);

        //7.新增提词文档记录 将专辑标题封装为提词文档保存到索引库
        this.saveSuggetInfo(albumInfoIndex);

        //8. 将新上架专辑ID存入到布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumId);
    }

    /**
     * 从索引库中删除专辑
     *
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        suggestIndexRepository.deleteById(albumId.toString());
    }


    /**
     * 站内专辑搜索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //一、构建检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.out.println("本次检索DSL");
            System.err.println(searchRequest.toString());

            //二、执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            //三、解析ES响应结果
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("[搜索服务]站内检索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";
    private static final String SUGGEST_INDEX_NAME = "suggestinfo";

    /**
     * 构建DSL,封装检索请求对象
     *
     * @param albumIndexQuery 检索条件
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索构建器对象 用于封装请求路径跟请求体参数
        SearchRequest.Builder builder = new SearchRequest.Builder();
        //1.1.封装请求地址中检索索引库名称 不指定会查询所有索引库
        builder.index(INDEX_NAME);

        //2.封装查询条件，设置请求体参数"query"参数
        //2.1 创建Bool查询条件对象封装三大查询条件
        BoolQuery.Builder allConditionBuilder = new BoolQuery.Builder();

        //2.2 设置关键字查询条件 包含三个子条件: 全文查询 专辑标题或专辑简介、或精确查询主播名称
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            allConditionBuilder.must(m -> m.bool(
                    b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                            .should(s -> s.match(m1 -> m1.field("albumIntro").query(keyword)))
                            .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
            ));
        }
        //2.2 设置三级分类过滤条件
        //2.1.1 设置1级分类过滤条件
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allConditionBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        //2.1.2 设置2级分类过滤条件
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allConditionBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        //2.1.3 设置3级分类过滤条件
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allConditionBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //2.4 设置标签过滤条件 每遍历一次得到一组标签过滤条件，每一组过滤条件封装一个nested查询
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            //标签条件=标签id:标签值id
            for (String attribute : attributeList) {
                String[] split = attribute.split(":");
                if (split != null && split.length == 2) {
                    allConditionBuilder.filter(
                            f -> f.nested(
                                    n -> n.path("attributeValueIndexList")
                                            .query(q -> q.bool(
                                                    b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                            .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                            ))
                            )
                    );
                }
            }
        }
        builder.query(allConditionBuilder.build()._toQuery());

        //3.封装分页，设置请求体参数"from","size"参数
        //计算起始位置=(页码-1)*页大小
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);

        //4.封装自定义排序规则，设置请求体参数"sort"参数
        //4.1 获取排序参数，前后端约定 排序字段:排序方式
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //4.2 按冒号进行分割获取排序字段和排序方式
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //4.3 获取真正排序字段，获取排序方式
                String orderField = "";
                switch (split[0]) {
                    case "1":
                        orderField = "hotScore";
                        break;
                    case "2":
                        orderField = "playStatNum";
                        break;
                    case "3":
                        orderField = "createTime";
                        break;
                }
                String finalOrderField = orderField;
                SortOrder sortOrder = "asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc;
                builder.sort(s -> s.field(f -> f.field(finalOrderField).order(sortOrder)));
            }
        }

        //5.封装高亮，设置请求体参数"highlight"参数
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", hf -> hf.preTags("<font style='color:red'>").postTags("</font>")));
        }

        //6.对检索响应字段进行指定，减少响应数据体，设置请求体参数"_source"参数
        builder.source(s -> s.filter(f -> f.excludes("attributeValueIndexList", "createTime", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum", "hotScore")));

        //3.调用构建器对象构建方法得到检索请求对象
        return builder.build();
    }

    /**
     * 执行ES检索后，解析ES响应结果
     *
     * @param searchResponse  ES响应对象
     * @param albumIndexQuery 查询条件
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建响应结果vo对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.封装分页相关四项数据
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.1 获取到总记录数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        //2.2 计算总页数
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        //2.3 为vo对象封装分页相关四项数据
        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        vo.setTotal(total);
        vo.setTotalPages(totalPages);
        //3. 封装检索到业务数据
        List<Hit<AlbumInfoIndex>> hitsList = hits.hits();
        if (CollectionUtil.isNotEmpty(hitsList)) {
            //3.1 采用Stream流处理将Hit对象转为AlbumInfoIndexVo
            List<AlbumInfoIndexVo> albumInfoIndexVoList = hitsList
                    .stream()
                    .map(hit -> {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.2 处理可能存在高亮片段
                        Map<String, List<String>> highlight = hit.highlight();
                        if (CollectionUtil.isNotEmpty(highlight)) {
                            List<String> list = highlight.get("albumTitle");
                            String albumTitleHighLigth = list.get(0);
                            albumInfoIndex.setAlbumTitle(albumTitleHighLigth);
                        }
                        //3.3 将AlbumInfoIndex转为VO
                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    }).collect(Collectors.toList());
            vo.setList(albumInfoIndexVoList);
        }
        //4.响应vo
        return vo;
    }

    /**
     * 获取置顶分类热门专辑列表
     *
     * @param category1Id 1级分类ID
     * @return [{baseCategory3：{分类对象},list:[热度前6专辑列表]},{置顶分类热门专辑Map对象}..]
     */
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        try {
            //1.远程调用专辑服务获取置顶7个三级分类列表 获取三级分类ID列表
            List<BaseCategory3> baseCategory3List = albumFeignClient.getTop7BaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "获取置顶7个三级分类列表失败");

            //1.1 将三级分类集合转为泛型：FieldValue
            List<FieldValue> fieldValueList = baseCategory3List
                    .stream()
                    .map(baseCategory3 -> FieldValue.of(baseCategory3.getId()))
                    .collect(Collectors.toList());

            //1.2 为了解析结果封装分类对象，将分类List集合转为Map  Map的Key就是三级分类ID Map的Value就是三级分类对象
            Map<Long, BaseCategory3> baseCategory3Map = baseCategory3List
                    .stream()
                    .collect(Collectors.toMap(c3 -> c3.getId(), c3 -> c3));

            //2.检索ES，查询条件：7个置顶三级分类ID，聚合：先根据三级分类ID聚合，设置子聚合根据热度排序获取前6个专辑
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDEX_NAME)
                            .size(0)
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations(
                                    "c3_agg", a -> a.terms(t -> t.field("category3Id").size(10))
                                            .aggregations(
                                                    "top6_agg", a1 -> a1.topHits(t -> t.sort(sort -> sort.field(f -> f.field("hotScore").order(SortOrder.Desc))).size(6).source(so -> so.filter(f -> f.excludes("attributeValueIndexList",
                                                            "createTime",
                                                            "subscribeStatNum",
                                                            "buyStatNum",
                                                            "commentStatNum",
                                                            "hotScore"))))
                                            )
                            )
                    , AlbumInfoIndex.class);
            //3.解析ES响应结果，封装置顶分类热门专辑列表
            //3.1 获取三级分类对应聚合结果对象
            Aggregate category3Aggregate = searchResponse.aggregations().get("c3_agg");
            //3.2 获取三级分类ID聚合结果bucket桶数组 每遍历一个Bucket封装置顶分类热门专辑Map对象
            Buckets<LongTermsBucket> category3Buckets = category3Aggregate.lterms().buckets();
            List<LongTermsBucket> category3BucketList = category3Buckets.array();
            if (CollectionUtil.isNotEmpty(category3BucketList)) {
                List<Map<String, Object>> list = category3BucketList
                        .stream()
                        .map(bucket -> {
                            //获取对应三级分类ID
                            long category3Id = bucket.key();
                            //3.3 从三级分类聚合对象bucket中获取“top6_agg”子聚合
                            List<Hit<JsonData>> top6AggHitList = bucket.aggregations().get("top6_agg").topHits().hits().hits();
                            //3.4 获取子聚合结果bucket桶数组 包含就是热度前6个专辑
                            if (CollectionUtil.isNotEmpty(top6AggHitList)) {
                                //3.4.1 将热度前6的专辑类型从Hit转为AlbumInfoIndex
                                List<AlbumInfoIndex> top6AlbumList = top6AggHitList
                                        .stream()
                                        .map(hit -> {
                                            String jsonSource = hit.source().toString();
                                            return JSON.parseObject(jsonSource, AlbumInfoIndex.class);
                                        }).collect(Collectors.toList());
                                //3.5 封装置顶分类热门专辑Map对象
                                Map<String, Object> map = new HashMap<>();
                                //TODO 如何根据三级分类ID拿到分类对象
                                map.put("baseCategory3", baseCategory3Map.get(category3Id));
                                map.put("list", top6AlbumList);
                                return map;
                            }
                            return null;
                        }).collect(Collectors.toList());
                return list;
            }
            return null;
        } catch (IOException e) {
            log.error("[搜索服务]聚合置顶分类热门专辑异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 新增提词文档到索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggetInfo(AlbumInfoIndex albumInfoIndex) {
        String albumTitle = albumInfoIndex.getAlbumTitle();
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        suggestIndex.setTitle(albumTitle);
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //将汉字转为拼音 jing dian l
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //将汉字获取拼音对应首字母
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
        suggestIndexRepository.save(suggestIndex);
    }

    /**
     * 根据用户已录入内容关键字(汉字、全拼、拼音首字母)自动补全 gdg
     *
     * @param keyword 关键字
     * @return ["郭德纲..","电炖锅"]
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.根据用户录入内容分别检索汉字、拼音、拼音首字母建议词
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(SUGGEST_INDEX_NAME)
                            .suggest(
                                    sg -> sg.suggesters("letter-suggest", fs -> fs.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true)))
                                            .suggesters("pinyin-suggest", fs -> fs.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true)))
                                            .suggesters("keyword-suggest", fs -> fs.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true)))
                            )
                    ,
                    SuggestIndex.class);

            //2.解析建议词结果将结果放入到一个集合（去重）中
            Set<String> suggestResult = new HashSet<>();
            //2.1 获取建议词检索结果对象
            suggestResult.addAll(this.parseSuggestResponse(searchResponse.suggest(), "letter-suggest"));
            suggestResult.addAll(this.parseSuggestResponse(searchResponse.suggest(), "pinyin-suggest"));
            suggestResult.addAll(this.parseSuggestResponse(searchResponse.suggest(), "keyword-suggest"));

            //3.判断建议词集合数量小于10个，采用全文查询专辑索引库补全到10个
            if (suggestResult.size() < 10) {
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(s -> s.index(INDEX_NAME).size(10).query(q -> q.match(m -> m.field("albumTitle").query(keyword))).source(s1 -> s1.filter(f -> f.includes("albumTitle"))), AlbumInfoIndex.class);
                List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
                if (CollectionUtil.isNotEmpty(hits)) {
                    for (Hit<AlbumInfoIndex> hit : hits) {
                        //3.1 获取全文查询得到专辑标题
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.2 将全文查询专辑标题加入提词结果
                        suggestResult.add(albumInfoIndex.getAlbumTitle());
                        if (suggestResult.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            //4.判断建议词响应结果
            if (suggestResult.size() >= 10) {
                return new ArrayList<>(suggestResult).subList(0, 10);
            }
            return new ArrayList<>(suggestResult);
        } catch (IOException e) {
            log.error("[专辑服务]建议词自动补全异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析建议词结果
     *
     * @param suggest     建议词响应对象
     * @param suggestName 自定义名称
     * @return
     */
    @Override
    public List<String> parseSuggestResponse(Map<String, List<Suggestion<SuggestIndex>>> suggest, String suggestName) {
        List<String> list = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestions = suggest.get(suggestName);
        for (Suggestion<SuggestIndex> suggestion : suggestions) {
            for (CompletionSuggestOption<SuggestIndex> option : suggestion.completion().options()) {
                SuggestIndex suggestIndex = option.source();
                list.add(suggestIndex.getTitle());
            }
        }
        return list;
    }

    /**
     * 更新Redis中小时榜专辑数据
     *
     * @return
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用专辑服务获取15个1级分类
            List<BaseCategory1> allCategoryList = albumFeignClient.getAllCategory1().getData();
            if (CollectionUtil.isNotEmpty(allCategoryList)) {
                //2.遍历15个一级分类ID，按照指定5种排序方式获取TOP10专辑列表
                for (BaseCategory1 baseCategory1 : allCategoryList) {
                    Long category1Id = baseCategory1.getId();
                    //构建Redis中Hash结构Key
                    String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
                    String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                    for (String dimension : rankingDimensionArray) {
                        //检索ES
                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                                s -> s.index(INDEX_NAME)
                                        .size(10)
                                        .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                        .sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc)))
                                        .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList", "createTime",
                                                "subscribeStatNum",
                                                "buyStatNum",
                                                "commentStatNum",
                                                "hotScore"))),
                                AlbumInfoIndex.class);
                        //解析ES响应结果
                        List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                        if (CollectionUtil.isNotEmpty(hitList)) {
                            List<AlbumInfoIndex> list = hitList.stream().map(Hit::source).collect(Collectors.toList());
                            //3.将TOP10专辑列表放入Redis的小时榜Hash中
                            //redisTemplate.opsForHash().put(key, dimension, list);
                            hashOps.put(dimension, list);
                        }
                    }
                }
            }
        } catch (IOException e) {
            log.error("[搜索服务]更新Redis中小时榜专辑数据异常：{}", e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 查询Redis中小时榜专辑列表
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndex> findRankingList(Long category1Id, String dimension) {
        //方式一：采用hash结构key+field操作
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        //List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, dimension);
        //方式二：采用绑定Hash操作对象 决定操作哪个Key
        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
        Boolean flag = hashOps.hasKey(dimension);
        if (flag) {
            return hashOps.get(dimension);
        }
        return null;
    }

    /**
     * 更新专辑统计数据
     *
     * @param trackStatMqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo trackStatMqVo) {
        try {
            Long albumId = trackStatMqVo.getAlbumId();
            elasticsearchClient.update(
                    u -> u.index(INDEX_NAME).id(albumId.toString())
                            .script(s -> s.inline(i -> i.lang("painless").source("ctx._source.playStatNum += params.increment").params(Map.of("increment", JsonData.of(trackStatMqVo.getCount())))))
                    , AlbumInfoIndex.class
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
